-
Notifications
You must be signed in to change notification settings - Fork 227
CountDownLatch Synchronization for RCPTestWorkbenchAdvisor #3429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
CountDownLatch Synchronization for RCPTestWorkbenchAdvisor #3429
Conversation
|
This pull request changes some projects for the first time in this development cycle. An additional commit containing all the necessary changes was pushed to the top of this PR's branch. To obtain these changes (for example if you want to push more changes) either fetch from your fork or apply the git patch. Git patchFurther information are available in Common Build Issues - Missing version increments. |
Test Results 3 018 files ±0 3 018 suites ±0 2h 38m 2s ⏱️ + 7m 48s For more details on these failures and errors, see this check. Results for commit 13a3215. ± Comparison against base commit 1a4d1a8. ♻️ This comment has been updated with latest results. |
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
....eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java
Outdated
Show resolved
Hide resolved
| if (!completed) { | ||
| System.err.println("Warning: Not all async/sync operations completed within timeout"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would let it fail. I'd rather have a test that fails consistently than one that might fail even if completed == false.
| if (!completed) { | |
| System.err.println("Warning: Not all async/sync operations completed within timeout"); | |
| } | |
| assertTrue(completed, "Not all async/sync operations completed within timeout"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it seems I missed an important part: when this fails, the workbench is already created and therefore subsequent attempts to create it will fail too.
After the latest changes, the logs show that once this happens, a bunch of tests fail with the error Workbench already exists and cannot be created again
Here are the first 3 failures.
Stack traces
org.eclipse.ui.tests.rcp.PlatformUITest.testCreateAndRunWorkbench -- Time elapsed: 7.070 s <<< FAILURE!
java.lang.AssertionError: Not all async/sync operations completed within timeout
at org.eclipse.ui.tests.harness.util.RCPTestWorkbenchAdvisor.postStartup(RCPTestWorkbenchAdvisor.java:221)
at org.eclipse.ui.tests.rcp.util.WorkbenchAdvisorObserver.postStartup(WorkbenchAdvisorObserver.java:140)
at org.eclipse.ui.internal.Workbench.lambda$18(Workbench.java:2901)
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1104)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1038)
at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153)
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:677)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
at org.eclipse.ui.tests.rcp.PlatformUITest.testCreateAndRunWorkbench(PlatformUITest.java:57)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
org.eclipse.ui.tests.rcp.PlatformUITest.testCreateAndRunWorkbenchWithExceptionOnStartup skipped
Running RcpTestSuite WorkbenchAdvisorTest
Tests run: 9, Failures: 0, Errors: 9, Skipped: 0, Time elapsed: 0.428 s <<< FAILURE! -- in RcpTestSuite WorkbenchAdvisorTest
org.eclipse.ui.tests.rcp.WorkbenchAdvisorTest.testEmptyProgressRegion -- Time elapsed: 0.191 s <<< ERROR!
java.lang.IllegalStateException: Workbench already exists and cannot be created again.
at org.eclipse.ui.internal.Workbench.<init>(Workbench.java:481)
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:609)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
at org.eclipse.ui.tests.rcp.WorkbenchAdvisorTest.testEmptyProgressRegion(WorkbenchAdvisorTest.java:293)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
org.eclipse.ui.tests.rcp.WorkbenchAdvisorTest.testFillAllActionBar -- Time elapsed: 0.031 s <<< ERROR!
java.lang.IllegalStateException: Workbench already exists and cannot be created again.
at org.eclipse.ui.internal.Workbench.<init>(Workbench.java:481)
at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:609)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:339)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:583)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:173)
at org.eclipse.ui.tests.rcp.WorkbenchAdvisorTest.testFillAllActionBar(WorkbenchAdvisorTest.java:279)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)The 1st failure is due to the timeout (Not all async/sync operations completed within timeout) and the workbench is not cleaned-up/shutdown. The following failing tests also fail with Workbench already exists and cannot be created again..
The way I see it, we need 2 things:
- See why the timeout occurs (is it "just" a matter of waiting a bit longer since the hardware is old and slow or is it something else?)
- Clean-up if the timeout occurs so that the following tests may start the workbench again.
I don't have any novel ideas at the moment :-\ and you @vogella ?
bebe15f to
cd9113d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change looks sound. Thank you for taking care of that long-standing failing test. I just have minor comments regarding a potential improvement/simplification.
| Display display = Display.getCurrent(); | ||
| if (display != null) { | ||
| // Process pending async events multiple times to ensure they execute | ||
| for (int i = 0; i < 10; i++) { | ||
| while (display.readAndDispatch()) { | ||
| // Keep processing events | ||
| } | ||
| // Small yield to allow any final async operations to complete | ||
| try { | ||
| Thread.sleep(10); | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| break; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this quite complex event processing really necessary? All relevant runnables are guaranteed to be scheduled here, so display.readAndDispatch() should not yield false until they all have been processed. You just need to make sure that those runnables that are not supposed to be executed during startup had the chance to accidentally be executed (to detect regression). So wouldn't be something like this be sufficient?
| Display display = Display.getCurrent(); | |
| if (display != null) { | |
| // Process pending async events multiple times to ensure they execute | |
| for (int i = 0; i < 10; i++) { | |
| while (display.readAndDispatch()) { | |
| // Keep processing events | |
| } | |
| // Small yield to allow any final async operations to complete | |
| try { | |
| Thread.sleep(10); | |
| } catch (InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| break; | |
| } | |
| } | |
| } | |
| UITestUtil.processEventsUntil(() -> syncWithDisplayAccess && asyncWithDisplayAccess, 5000); | |
| UITestUtil.processEvents(); |
| private static boolean started = false; | ||
|
|
||
| // Use a CountDownLatch to ensure async operations complete before postStartup | ||
| private static final CountDownLatch asyncLatch = new CountDownLatch(4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: wouldn't it be cleaner to use something like a Phaser and make every thread register itself and then deregister/arrive when being executed (like now done with asyncLatch.countDown()), so that the latch's count does not need to magically match the number of thread created?
…tion Replace CountDownLatch with Phaser to eliminate magic number and simplify event processing using UITestUtil helpers per review feedback. - Use Phaser instead of CountDownLatch for cleaner thread tracking - Simplify postStartup() event loop with UITestUtil.processEventsUntil() - Each thread self-registers with phaser, removing hardcoded count 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
cd9113d to
13a3215
Compare
The flaky test was caused by a race condition in
RCPTestWorkbenchAdvisor.java. The test spawns 4 background threads during preStartup() that schedule async/sync runnables on the display.
However, there was no guarantee these runnables would execute before postStartup() marked the workbench as "started" (by setting started = true).
This meant asyncWithDisplayAccess could still be null when the test assertions ran, causing intermittent failures.
This change adds a CountDownLatch to ensure all 4 async/sync operations complete before marking the workbench as started:
Key changes to
/tests/org.eclipse.ui.tests.harness/src/org/eclipse/ui/tests/harness/util/RCPTestWorkbenchAdvisor.java:
- private static java.util.concurrent.CountDownLatch asyncLatch = null;
- asyncLatch = new java.util.concurrent.CountDownLatch(4); — tracks 4 operations
- In setupSyncDisplayThread(): Added finally block (lines 179-183) that calls asyncLatch.countDown()
- In setupAsyncDisplayThread(): Added finally block (lines 205-209) that calls asyncLatch.countDown()
- Waits up to 5 seconds for all operations to complete
- Logs warnings if timeout occurs or thread is interrupted
- Only marks started = true after all operations complete
Fixes #1517